package com.testing.web;

import com.google.common.io.Files;
import com.testing.DriverSelf.FFDriver;
import com.testing.DriverSelf.GoogleDriver;
import com.testing.DriverSelf.GoogleMobileDriver;
import com.testing.common.AutoUtils;
import com.testing.common.MysqlUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Classname WebKeywords
 * @Description web自动化关键字类其中封装在进行网页操作的时候的基本操作方法，也就是这是PO模式中的根页面BasePage
 * @Date 2023/10/12 20:42
 * @Created by 特斯汀Roy
 */
public class WebKeywords extends AutoUtils {
    //大家都要用的driver
    private WebDriver driver;

    /**
     * 启动浏览器的方法
     *
     * @param browserType 浏览器类型  可以是 chrome 、FF、edge
     * @return
     */
    public boolean open(String browserType) {
        log.info("打开" + browserType + "浏览器");
        try {
            switch (browserType) {
                case "FF":
                    System.setProperty("webdriver.gecko.driver", "DriverExe\\geckodriver.exe");
                    driver = new FirefoxDriver();
                    break;
                case "edge":
                    System.setProperty("webdriver.edge.driver", "DriverExe\\msedgedriver.exe");
                    driver = new EdgeDriver();
                    break;
                case "mobile":
                    GoogleMobileDriver gm = new GoogleMobileDriver("DriverExe\\chromedriver.exe");
                    driver = gm.getDriver();
                    break;
                case "chrome":
                default:
                    GoogleDriver gg = new GoogleDriver("DriverExe\\chromedriver.exe");
                    driver = gg.getDriver();
                    break;
            }
            //隐式等待
            //设置一个等待的最长期限，在这个期限内，如果元素能够被定位到，结束等待，继续执行
            //否则超时之后再提示没有找到元素。
            //隐式等待会在周期内反复尝试查找元素，频率是500ms。
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
            return true;
        } catch (Exception e) {
            log.error("启动浏览器失败！", e);
            return false;
        }
    }

    /**
     * 显式等待，等待标题出现某个内容
     *
     * @param contents 预期出现的内容
     */
    public void waitTitleContains(String contents) {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        wait.until(ExpectedConditions.titleContains(contents));
    }

    /**
     * 访问对应的网页
     *
     * @param url
     * @return
     */
    public boolean visitWeb(String url) {
        try {
            driver.get(url);
            log.info("访问" + url);
            return true;
        } catch (Exception e) {
            log.error("访问" + url + "出错", e);
            return false;
        }
    }

    /**
     * 调整浏览器大小
     */
    public void setSize() {
        Point point = new Point(293, 22);
        driver.manage().window().setPosition(point);
        //设置大小
        Dimension dimension = new Dimension(1608, 988);
        driver.manage().window().setSize(dimension);
    }

    /**
     * 根据自己的喜好来设计定位方法的使用。
     * 只使用css和xpath两种
     * xpath开头只有两种写法：一定是以/开头，有可能出现(开头
     *
     * @param locator 定位表达式
     * @return
     */
    public WebElement findElement(String locator) {
        if (locator.startsWith("/") || locator.startsWith("(")) {
            //输入的是xpath表达式
            return driver.findElement(By.xpath(locator));
        } else {
            return driver.findElement(By.cssSelector(locator));
        }
    }

    /**
     * 点击方法
     *
     * @param locator
     * @return
     */
    public boolean click(String locator) {
        try {
            locator = useParam(locator);
            log.info("点击" + locator + "元素");
            findElement(locator).click();
            return true;
        } catch (Exception e) {
            log.error("点击" + locator + "元素失败", e);
            String fileLocator = locator.replaceAll("[\\/\\\\\\.\\:\\*\\?\\<\\>]", "!");
            takeScreenShot("点击"+fileLocator+"元素");
            return false;
        }
    }

    public boolean submit(String locator) {
        try {
            locator = useParam(locator);
            log.info("提交" + locator + "元素的表单");
            findElement(locator).submit();
            return true;
        } catch (Exception e) {
            log.error("提交" + locator + "元素表单失败", e);
            String fileLocator = locator.replaceAll("[\\/\\\\\\.\\:\\*\\?\\<\\>]", "!");
            takeScreenShot("提交"+fileLocator+"元素");
            return false;
        }
    }



    /**
     * 输入方法
     *
     * @param locator 定位表达式，只支持xpath和css
     * @param content 内容
     * @return
     */
    public boolean input(String locator, String content) {
        try {
            locator = useParam(locator);
            content = useParam(content);
            log.info("正在向" + locator + "输入" + content);
            findElement(locator).sendKeys(content);
            return true;
        } catch (Exception e) {
            log.error("向" + locator + "输入失败", e);
            String fileLocator = locator.replaceAll("[\\/\\\\\\.\\:\\*\\?\\<\\>]", "!");
            takeScreenShot("输入"+fileLocator+"元素");
            return false;
        }
    }

    /**
     * 将鼠标移动到对应元素上。
     *
     * @param locator
     * @return
     */
    public boolean hover(String locator) {
        try {
            WebElement element = findElement(locator);
            Actions actions = new Actions(driver);
            actions.moveToElement(element).perform();
            log.info("将鼠标移动到" + locator + "元素上");
            return true;
        } catch (Exception e) {
            log.error("悬停到指定元素出错", e);
            String fileLocator = locator.replaceAll("[\\/\\\\\\.\\:\\*\\?\\<\\>]", "!");
            takeScreenShot("悬停"+fileLocator+"元素");
            return false;
        }
    }

    /**
     * 下拉框选择方法
     *
     * @param locator Select元素定位表达式
     * @param text    选项的文本内容。
     * @return
     */
    public boolean selectByText(String locator, String text) {
        try {
            Select select = new Select(findElement(locator));
            select.selectByVisibleText(text);
            log.info("在" + locator + "下拉框中选择" + text);
            return true;
        } catch (Exception e) {
            log.error("在" + locator + "下拉框中选择" + text + "失败", e);
            return false;
        }
    }

    /***************************************Robot操作方法*********************************************/
    public boolean robotClick(String x, String y) {
        try {
            Robot robot = new Robot();
            robot.mouseMove(Integer.parseInt(x), Integer.parseInt(y));
            robot.mousePress(KeyEvent.BUTTON1_DOWN_MASK);
            robot.mouseRelease(KeyEvent.BUTTON1_DOWN_MASK);
            log.info("用robot操作鼠标进行点击");
            return true;
        } catch (Exception e) {
            log.info("用robot点击失败");
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 通过robot操作剪贴板完成输入操作，实际上就是ctrl+v
     *
     * @param content 要输入的内容
     * @return
     */
    public boolean robotInput(String content) {
        try {
            Robot robot = new Robot();
            //创建剪贴板
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            //在剪贴板中设置自己要输入的内容
            StringSelection clip = new StringSelection(content);
            clipboard.setContents(clip, null);
            robot.keyPress(KeyEvent.VK_CONTROL);
            robot.keyPress(KeyEvent.VK_V);
            robot.keyRelease(KeyEvent.VK_CONTROL);
            robot.keyRelease(KeyEvent.VK_V);
            log.info("使用robot输入" + content);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("使用robot输入" + content + "失败");
            return false;
        }
    }

    /**
     * 按下回车键
     *
     * @return
     */
    public boolean robotEnter() {
        try {
            Robot robot = new Robot();
            robot.keyPress(KeyEvent.VK_ENTER);
            robot.keyRelease(KeyEvent.VK_ENTER);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**********************************js运行方法*********************************************/

    /**
     * 直接执行js语句。
     *
     * @return
     */
    public boolean runJs(String jsScript) {
        try {
            JavascriptExecutor jsRunner = (JavascriptExecutor) driver;
            jsRunner.executeScript(jsScript);
            log.info("执行js语句" + jsScript);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("执行js语句" + jsScript + "失败");
            return false;
        }
    }

    /**
     * 定位元素之后，对这个元素执行指定操作。
     *
     * @param locator 定位元素用的表达式
     * @param action  只需要元素后面的操作语句
     * @return
     */
    public boolean runJsWithElement(String locator, String action) {
        try {
            WebElement element = findElement(locator);
            JavascriptExecutor jsRunner = (JavascriptExecutor) driver;
            //通过js语句中添加return可以得到返回结果。
            Object result = jsRunner.executeScript("return arguments[0]." + action, element);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 通过序号来切换到对应的窗口
     *
     * @param index 从0开始计数。
     * @return
     */
    public boolean switchWindowByIndex(String index) {
        try {
            Set<String> windowHandles = driver.getWindowHandles();
            List<String> handleList = new ArrayList<>(windowHandles);
            int i = Integer.parseInt(index);
            log.info("切换时，list是" + handleList);
            String targetHandle = handleList.get(i);
            log.info("要切的是" + targetHandle);
            driver.switchTo().window(targetHandle);
            log.info("切换到第" + index + "个窗口");
            return true;
        } catch (Exception exception) {
            exception.printStackTrace();
            log.info("切换窗口失败");
            return false;
        }
    }


    /**
     * 方法有局限性，必须保持操作窗口只剩一个。
     */
    public boolean closeOldThenToNew() {
        try {
            //先获取旧窗口的句柄
            String oldHandle = driver.getWindowHandle();
            //关闭当前所在的旧窗口
            driver.close();
            Set<String> windowHandles = driver.getWindowHandles();
            //不是旧的窗口的就是新的。
            for (String windowHandle : windowHandles) {
                if (!windowHandle.equals(oldHandle)) {
                    driver.switchTo().window(windowHandle);
                    break;
                }
            }
            return true;
        } catch (Exception e) {
            log.error("关闭旧窗口后切换到新窗口失败", e.fillInStackTrace());
            return false;
        }
    }



    //作业，通过标题来切换不同的窗口。
    //需要先遍历切换到窗口，然后获取标题，判断是否和需要的标题一致。
    public boolean switchWindow(String title) {
        try {
            Set<String> windowHandles = driver.getWindowHandles();
            for (String windowHandle : windowHandles) {
                //            找到每个句柄，切换到对应的窗口
                driver.switchTo().window(windowHandle);
                //如果包含的话，就已经找到了，不继续切换
                if (driver.getTitle().contains(title)) {
                    break;
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 切换到iframe元素中
     *
     * @param locator 可以是id或者是定位表达式。
     * @return
     */
    public boolean switchIframe(String locator) {
        try {
            //如果有xpath和css选择器中的特殊字符，那么就用元素定位的方式切换
            if (locator.startsWith("/") || locator.contains("#") || locator.contains("[")) {
                WebElement iframeEle = findElement(locator);
                driver.switchTo().frame(iframeEle);
            } else {
                //否则用id方式切换
                driver.switchTo().frame(locator);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("切换Iframe失败");
            return false;
        }
    }

    /**
     * 切换到父级Iframe
     *
     * @return
     */
    public boolean switchToParent() {
        try {
            driver.switchTo().parentFrame();
            log.info("切换到上级iframe");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("切换到上级iframe失败");
            return false;
        }
    }

    /**
     * 切换到html根层级
     *
     * @return
     */
    public boolean switchToTop() {
        try {
            driver.switchTo().defaultContent();
            log.info("切换到顶层html");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("切换到顶层html失败");
            return false;
        }
    }

    /***************************************截图方法********************************************/
    /**
     * 截取整个浏览器的内容作为一张图片
     *
     * @param fileName
     */
    public void takeScreenShot(String fileName) {
        try {
            fileName = dateStr("MMdd HH：mm") + fileName;
            //生成一个临时文件，这时候是在内存中的一个文件对象。
            File screenshotAs = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            //基于文件目录，创建一个文件，这也是在内存里面。
            File picFile = new File("log/pic/" + fileName + ".png");
            log.info("截图保存到log/pic/" + fileName + ".png文件下");
            //将截图的内容复制给指定的路径的文件，同时完成这个文件在磁盘上的创建。
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截图失败", e);
        }
    }


    /***
     * 将元素截成一张图片。
     * @param locator
     * @param fileName
     */
    public void takeEleScreenShot(String locator, String fileName) {
        try {
            WebElement element = findElement(locator);
            fileName = dateStr("MMdd HH：mm") + fileName;
            File screenshotAs = element.getScreenshotAs(OutputType.FILE);
            //基于文件目录，创建一个文件，这也是在内存里面。
            File picFile = new File("log/pic/" + fileName + ".png");
            log.info("截图保存到log/pic/" + fileName + ".png文件下");
            //将截图的内容复制给指定的路径的文件，同时完成这个文件在磁盘上的创建。
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截取" + locator + "元素图片失败", e);
        }
    }


    /******************************************断言操作**********************************************/

    /**
     * 断言标题是否和预期结果完全相同
     *
     * @param expectedResult 预期结果
     * @return 断言是否通过
     */
    public boolean assertTitleIs(String expectedResult) {
        String title = driver.getTitle();
        if (title.equals(expectedResult)) {
            log.info("测试成功，标题是" + expectedResult);
            return true;
        } else {
            log.info("测试失败，标题是" + title + "，预期结果是" + expectedResult);
            return false;
        }
    }

    /**
     * 断言标题是否包含预期结果
     *
     * @param expectedResult 预期结果
     * @return 断言是否通过
     */
    public boolean assertTitleContains(String expectedResult) {
        String title = driver.getTitle();
        if (title.contains(expectedResult)) {
            log.info("测试成功，标题是" + expectedResult);
            return true;
        } else {
            log.info("测试失败，标题是" + title + "，不包含预期结果：" + expectedResult);
            return false;
        }
    }

    /**
     * 判断一个元素的文本内容是否包含预期结果，这个可以用来判断一个元素是否在页面上。
     *
     * @param locator        定位表达式
     * @param expectedResult 预期结果
     * @return
     */
    public boolean assertElementTextContains(String locator, String expectedResult) {
        try {
            locator = useParam(locator);
            expectedResult = useParam(expectedResult);
            String text = findElement(locator).getText();
            if (text.contains(expectedResult)) {
                log.info("测试成功，元素文本是" + expectedResult);
                return true;
            } else {
                log.info("测试失败，元素文本是" + text + "，不包含预期结果：" + expectedResult);
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("测试失败，没有找到这个元素");
            return false;
        }
    }

    /**
     * 断言元素某个属性的值是预期值
     *
     * @param locator        定位表达式
     * @param attribute      属性
     * @param expectedResult 预期结果
     * @return
     */
    public boolean assertElementAttribute(String locator, String attribute, String expectedResult) {
        try {
            locator = useParam(locator);
            attribute = useParam(attribute);
            expectedResult = useParam(expectedResult);
            String value = findElement(locator).getAttribute(attribute);
            if (value.contains(expectedResult)) {
                log.info("测试成功，元素" + attribute + "属性是" + expectedResult);
                return true;
            } else {
                log.info("测试失败，元素" + attribute + "属性是：" + value + "，不包含预期结果：" + expectedResult);
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("测试失败，没有找到这个元素");
            return false;
        }
    }

    /**
     * sql语句能够查到结果就是测试通过。
     *
     * @return
     */
    public boolean assertDbExists(String sql) {
        sql = useParam(sql);
        MysqlUtils mysql = new MysqlUtils();
        List<Map<String, String>> maps = mysql.queryDbData(sql);
        log.info("查询的结果是：" + maps);
        if (maps.size() > 0) {
            if (maps.get(0).get("error") != null && maps.get(0).get("error").equals("查询数据失败")) {
                log.info("测试失败，查询过程出错了");
                return false;
            }
            log.info("查询有数据，测试成功");
            log.info("数据是" + maps);
            return true;
        } else {
            log.info("没查到数据，测试失败");
            return false;
        }
    }


    public boolean quit() {
        try {
            log.info("关闭浏览器");
            driver.quit();
            return true;
        } catch (Exception e) {
            log.info("关闭浏览器失败");
            e.printStackTrace();
            return false;
        }
    }


    //便于外部调用的时候获取webKeywords实例化完成启动的浏览器。
    public WebDriver getDriver() {
        return driver;
    }

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }
}
